/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief PNG decoder interface using the libpng library.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "PNGDecoder.hpp"
#include "owndebug.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>

//******************************************************************************
//******************************************************************************
#define PNGDECODER_RGBBUFFER_BLOCKSIZE 1024
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

CPNGDecoder::CPNGDecoder(const char *aData, const int aDataLen, bool aCopy)
  : iPNGData(NULL),
    iPNGDataSize(0),
    iPNGDataReserved(false),
    iPNGDataOwned(false),
    iRawData(NULL),
    iRawDataSize(0),
    iRawDataReserved(0),
    iPNGDecoderPtr(NULL),
    iPNGInfoPtr(NULL),
    iGotHeader(false),
    iGotRaw(false),
    iWidth(-1),
    iHeight(-1),
    iColorType(-1),
    iBitDepth(-1),
    iReadDataIndex(0)
{
  
  // Last but not least; assign the source pointer if provided.
  if (aData) {
    SetPNGDataPtr(aData, aDataLen, aCopy);
  }
}
//******************************************************************************

CPNGDecoder::~CPNGDecoder()
{
  // Delete the RGB data (if allocated)
  if (iRawData) {
    delete[] iRawData;
    iRawData = NULL;
  }

  // Delete PNG data (if allocated by us)
  if (iPNGData && iPNGDataOwned) {
    delete[] iPNGData;
    iPNGData = NULL;
  }

  // Delete png decoder.
  if (iPNGDecoderPtr) {
    png_destroy_read_struct(&iPNGDecoderPtr,&iPNGInfoPtr,NULL);
    iPNGDecoderPtr = NULL;
    iPNGInfoPtr = NULL;
  }
}
//******************************************************************************

bool CPNGDecoder::SetPNGDataPtr(const char *aData, const int aDataLen, bool aCopy)
{
  // Is Copy requested?
  if (aCopy) {
    // If data was not previously owned, clear out pointer to allow
    // proper allocation.
    if (!iPNGDataOwned) {
      if (iPNGData) iPNGData = NULL;
    }
    
    // Copy requested, so copy the memory region pointed.
    iPNGDataOwned = true;
    iPNGDataSize = aDataLen;
    ReservePNGData(aDataLen);
    memcpy(iPNGData, aData, aDataLen);

  } else {
    // No copy; hence free the possible existing memory block.
    if (iPNGDataOwned && iPNGData) {
      delete[] iPNGData;
      iPNGDataReserved = 0;
    }

    // No copy requested; assign pointer only.
    iPNGDataOwned = false;
    iPNGData = const_cast<char *>(aData);
    iPNGDataSize = aDataLen;
  }

  // Got Pointers, call to set up initial Decode.
  return InitDecode();
}
//******************************************************************************

void CPNGDecoder::ReservePNGData(const unsigned int aSizeBytes)
{
  // Buffer must be owned prior call.
  assert(iPNGDataOwned == true);
  if (iPNGDataReserved < aSizeBytes) {
    if (iPNGData) delete[] iPNGData;
    iPNGDataReserved = aSizeBytes;
    iPNGData = new char [aSizeBytes];
  }
}
//******************************************************************************

void CPNGDecoder::ReserveRawData(const unsigned int aSizeBytes)
{
  // Buffer always owned
  if (iRawDataReserved < aSizeBytes) {
    if (iRawData) delete[] iRawData;
    iRawDataReserved = aSizeBytes;
    iRawData = new char [aSizeBytes];
  }
}
//******************************************************************************

bool CPNGDecoder::InitDecode()
{
  const unsigned int KPngSignatureLength = 8;
  bool result = false;

  // Check pointers and size for status.
  if (iPNGData && iPNGDataSize > KPngSignatureLength) {
    png_byte *pngSignature = (png_byte*)iPNGData;

    if(png_check_sig(pngSignature, KPngSignatureLength)) {
      // SIG ok. Proceed.
      iReadDataIndex += KPngSignatureLength; // Mark 8 first bytes read.

      // IF pointer is valid, destroy it first. (FIXME: Is it possible to reuse?)
      if (iPNGDecoderPtr) {
        png_destroy_read_struct(&iPNGDecoderPtr,&iPNGInfoPtr,NULL);
        iPNGDecoderPtr = NULL;
        iPNGInfoPtr = NULL;
      }

      // get PNG file info struct (memory is allocated by libpng)
      iPNGDecoderPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
      if (iPNGDecoderPtr) {
        // get PNG image data info struct (memory is allocated by libpng)
        iPNGInfoPtr = png_create_info_struct(iPNGDecoderPtr);
        
        if(iPNGInfoPtr) {
          // Register CallBack.
          png_set_read_fn(iPNGDecoderPtr, this, ReadDataFromInputStream);

          // Mark SIG already read.
          png_set_sig_bytes(iPNGDecoderPtr, KPngSignatureLength);
         
          // Well - now we should be set! InitDecode OK.
          result = true;
 
        } else {
          // libpng must free file info struct memory before we bail
          png_destroy_read_struct(&iPNGDecoderPtr, NULL, NULL);

        }
      }
    } 
  }
 
  // If result was FALSE, mark datasize as zero. (following phases
  // will fail immediately if attempted)
  if (!result) iPNGDataSize = 0;
 
  return result;
}
//******************************************************************************

bool CPNGDecoder::DecodeHeader()
{
  // Mark got header as false initially.
  iGotHeader = false;

  if (iPNGData && iPNGDataSize) {
    
    // Read the header.
    png_read_info(iPNGDecoderPtr, iPNGInfoPtr);
    
    // Some variables.
    png_uint_32 width = 0;
    png_uint_32 height = 0;
    int bitDepth = 0;
    int colorType = -1;
    png_uint_32 retval = png_get_IHDR(iPNGDecoderPtr, iPNGInfoPtr,
                                      &width,
                                      &height,
                                      &bitDepth,
                                      &colorType,
                                      NULL, NULL, NULL);
    
    if (retval == 1) {
      // Succesfully read!
      iWidth = width;
      iHeight = height;
      iBitDepth = bitDepth;
      iColorType = colorType;
      iGotHeader = true;
      dPrint(ODDEBUG(0),"Header succesfully read: Width: %u, Height: %u, bitDepth: %d, ColorType: %d",
             iWidth, iHeight, iBitDepth, iColorType);
    }
  }

  return iGotHeader;
}
//******************************************************************************

bool CPNGDecoder::Decode()
{
  bool result = false;
  // Check for header, and do decoding with it first
  if (!iGotHeader) DecodeHeader();

  // Mark GotRGB as false.
  iGotRaw = false;

  // Check
  if (iPNGData && iPNGDataSize && iGotHeader) {

    const unsigned int height = iHeight;
    const png_uint_32 bytesPerRow = png_get_rowbytes(iPNGDecoderPtr, iPNGInfoPtr);
    
    // Calculate needed space and reserve
    iRawDataSize = bytesPerRow * height;
    ReserveRawData(iRawDataSize);
    
    // Pointer to beginning of row.
    char *rowData = iRawData;
  
    // read single row at a time; directly to internal RGB buffer.
    for(unsigned int rowIdx = 0; rowIdx < height; ++rowIdx) {
      // Get ROW, and store directly
      png_read_row(iPNGDecoderPtr, (png_bytep)rowData, NULL);
      
      // increment rowData pointer by size of a single row.
      rowData += bytesPerRow;
      
    }

    result = iGotRaw = true;
  }

  return result;
}
//******************************************************************************

int CPNGDecoder::GetImageWidth(void) 
{
  return iGotHeader ? iWidth : -1;
}
//******************************************************************************

int CPNGDecoder::GetImageHeight(void)
{
  return iGotHeader ? iHeight : -1;
}
//******************************************************************************

const char *CPNGDecoder::GetRGBDataPtr(void)
{
  return iGotRaw&&(iColorType==PNG_COLOR_TYPE_RGB||iColorType==PNG_COLOR_TYPE_RGBA) ? iRawData : NULL;
}
//******************************************************************************

bool CPNGDecoder::GetRGBDataCopy(char *aDataPtr, const unsigned int aMaxBufSize)
{
  bool result = false;
  const char *ptr = GetRawDataPtr();
  if (ptr && aMaxBufSize >= iRawDataSize && (iColorType==PNG_COLOR_TYPE_RGB||iColorType==PNG_COLOR_TYPE_RGBA)) {
    memcpy(aDataPtr, iRawData, iRawDataSize);
    result = true;
  }
  return result;
}
//******************************************************************************

int CPNGDecoder::GetRGBDataSize(void)
{
  return iGotHeader&&(iColorType==PNG_COLOR_TYPE_RGB||iColorType==PNG_COLOR_TYPE_RGBA) ? 
    (iWidth * iHeight * (iColorType == PNG_COLOR_TYPE_RGB ? 3 : 4)) : -1;
}
//******************************************************************************

const char *CPNGDecoder::GetRawDataPtr(void)
{
  return iGotRaw ? iRawData : NULL;
}
//******************************************************************************

bool CPNGDecoder::GetRawDataCopy(char *aDataPtr, const unsigned int aMaxBufSize)
{
  bool result = false;
  const char *ptr = GetRawDataPtr();
  if (ptr && aMaxBufSize >= iRawDataSize) {
    memcpy(aDataPtr, iRawData, iRawDataSize);
    result = true;
  }
  return result;  
}
//******************************************************************************

int CPNGDecoder::GetRawDataSize(void)
{
  return iGotHeader ? iRawDataSize : -1; 
}
//******************************************************************************

int CPNGDecoder::GetColorType(void)
{
  return iGotHeader ? iColorType : -1;
}
//******************************************************************************

int CPNGDecoder::GetBitDepth(void)
{
  return iGotHeader ? iBitDepth : -1;
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

void CPNGDecoder::ReadDataFromInputStream(png_structp png_ptr, 
                                          png_bytep outBytes,
                                          png_size_t byteCountToRead)
{
  assert(png_ptr->io_ptr != NULL);

  // Convert pointer to class.
  CPNGDecoder *c = (CPNGDecoder *)png_ptr->io_ptr;
    
  // Read bytes; First check that data available >= requested bytes.
  if (byteCountToRead + c->iReadDataIndex <= c->iPNGDataSize) {

    // Copy data to outBytes buffer.
    memcpy(outBytes, c->iPNGData+c->iReadDataIndex, byteCountToRead);

    // Update index.
    c->iReadDataIndex += byteCountToRead;
    
  } else {
    // Not enough data!
    dPrint(ODVINFO,
           "Requested %d bytes, only have %d left. Read failed.",
           byteCountToRead, c->iPNGDataSize - c->iReadDataIndex);
    
  }
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

